#include "MainWindowViewModel.hpp"

//#include <CXSDK/GLDString.hpp>

#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <QVariant>
#include <QtConcurrent/QtConcurrent>
#include <QProgressBar>
#include <QtWidgets/qmessagebox.h>

MainWindowViewModel *MainWindowViewModel::mainWindowViewModel = nullptr;

MainWindowViewModel::MainWindowViewModel()
{
    osgbTool = new OSGBTool;
    modelWidget = new ModelWidget;
}

MainWindowViewModel *MainWindowViewModel::getInstance()
{
    if (mainWindowViewModel == nullptr)
    {
        mainWindowViewModel = new MainWindowViewModel;
    }

    return mainWindowViewModel;
}

std::string NNUOpenCIMComponent_ConvertComponentTypeToStr(const NNU::OpenCIM::Component::ComponentType& tmpComponentType)
{
    switch (tmpComponentType)
    {
    case NNU::OpenCIM::Component::ComponentType::PROPERTY :

        return "NNU::OpenCIM::Component::ComponentType::Property";

    case NNU::OpenCIM::Component::ComponentType::SEMANTICS :

        return "NNU::OpenCIM::Component::ComponentType::Semantics";

    case NNU::OpenCIM::Component::ComponentType::SPATIALGEOMETRY :

        return "NNU::OpenCIM::Component::ComponentType::SpatialGeometry";

    case NNU::OpenCIM::Component::ComponentType::SPATIALLOCATION :

        return "NNU::OpenCIM::Component::ComponentType::SpatialLocation";

    case NNU::OpenCIM::Component::ComponentType::SPATIALSYSTEM :

        return "NNU::OpenCIM::Component::ComponentType::SpatialSystem";

    case NNU::OpenCIM::Component::ComponentType::TEMPORALPOINT :

        return "NNU::OpenCIM::Component::ComponentType::TemporalPoint";

    case NNU::OpenCIM::Component::ComponentType::TEMPORALRANGE :

        return "NNU::OpenCIM::Component::ComponentType::TemporalRange";

    case NNU::OpenCIM::Component::ComponentType::TEMPORALSYSTEM :

        return "NNU::OpenCIM::Component::ComponentType::TemporalSystem";

    case NNU::OpenCIM::Component::ComponentType::UNKNOWCMP :

        return "NNU::OpenCIM::Component::ComponentType::UnknowCMP";
    }

    return "Unknown";
}

std::string NNUOpenCIMComponent_ConvertSpatialGeometryTypeToStr(const NNU::OpenCIM::Component::SpatialGeometryType& tmpSpatialGeometryType)
{
    switch (tmpSpatialGeometryType)
    {
    case NNU::OpenCIM::Component::SpatialGeometryType::BREP :

        return "NNU::OpenCIM::Component::SpatialGeometryType::Brep";

    case NNU::OpenCIM::Component::SpatialGeometryType::EXTERNALGEO :

        return "NNU::OpenCIM::Component::SpatialGeometryType::ExternalGeo";

    case NNU::OpenCIM::Component::SpatialGeometryType::SWEPT :

        return "NNU::OpenCIM::Component::SpatialGeometryType::Swept";

    case NNU::OpenCIM::Component::SpatialGeometryType::UNKNOWNGEO :

        return "NNU::OpenCIM::Component::SpatialGeometryType::UnknownGeo";
    }

    return "Unknown";
}

std::string NNUOpenCIMCommon_ConvertDataFormatTypeToStr(const NNU::OpenCIM::Common::DataFormat& tmpDataFormatType)
{
    switch (tmpDataFormatType)
    {
    case NNU::OpenCIM::Common::DataFormat::EXCEL :

        return "NNU::OpenCIM::Common::DataFormat::Excel";

    case NNU::OpenCIM::Common::DataFormat::IFC :

        return "NNU::OpenCIM::Common::DataFormat::Ifc";

    case NNU::OpenCIM::Common::DataFormat::JPG :

        return "NNU::OpenCIM::Common::DataFormat::Jpg";

    case NNU::OpenCIM::Common::DataFormat::JSON :

        return "NNU::OpenCIM::Common::DataFormat::Json";

    case NNU::OpenCIM::Common::DataFormat::LAS :

        return "NNU::OpenCIM::Common::DataFormat::Las";

    case NNU::OpenCIM::Common::DataFormat::OBJ :

        return "NNU::OpenCIM::Common::DataFormat::Obj";

    case NNU::OpenCIM::Common::DataFormat::OSGB :

        return "NNU::OpenCIM::Common::DataFormat::Osgb";

    case NNU::OpenCIM::Common::DataFormat::PNG :

        return "NNU::OpenCIM::Common::DataFormat::Png";

    case NNU::OpenCIM::Common::DataFormat::SHP :

        return "NNU::OpenCIM::Common::DataFormat::Shp";

    case NNU::OpenCIM::Common::DataFormat::TIF :

        return "NNU::OpenCIM::Common::DataFormat::Tif";

    case NNU::OpenCIM::Common::DataFormat::TXT :

        return "NNU::OpenCIM::Common::DataFormat::Txt";

    case NNU::OpenCIM::Common::DataFormat::UNKNOWNDAT :

        return "NNU::OpenCIM::Common::DataFormat::UnknownDat";
    }

    return "Unknown";
}

//void MainWindowViewModel_AddObjToOsgViewer(double rx, double ry, double rz, const QString& name, const QString& objFilePath,
//    double longitude, double latitude, double height, double scale, const QString& epsg, const QString& innerUrl)
//{
//    QFileInfo objPath(objFilePath);
//    auto baseName = objPath.baseName();
//
//    osg::ref_ptr<osg::Group> model = new osg::Group;
//
//    if (objPath.suffix() == "ifc")
//    {
//        int i = 0;
//        for (const auto& temp : MainWindowViewModel::getInstance()->ifc2objList) {
//            if (temp.contains(baseName)) {
//                break;
//            }
//            ++i;
//        }
//
//        /********************************************************************************
//         * 这里需要将IFC进行解析并加载
//         * 由于各家IFC版本不同，解析方法不同，因此这里提前将IFC解析为OBJ格式并存放于exe所在的Temp文件夹下
//         * 使用时需要根据实际情况进行调整
//         ********************************************************************************/
//
//        QFileInfo objFileInfo(QCoreApplication::applicationDirPath() + "/temp/" + baseName + ".obj");
//        QFileInfo mtlFileInfo(QCoreApplication::applicationDirPath() + "/temp/" + baseName + ".mtl");
//
//        auto tempObjList = drawObjNode(objFileInfo.absoluteFilePath(), mtlFileInfo.absoluteFilePath());
//        MainWindowViewModel::getInstance()->ifc2objList.push_back(tempObjList);
//
//        auto innerList = innerUrl.split(",");
//        for (const auto& tempInner : innerList)
//        {
//            model->addChild(MainWindowViewModel::getInstance()->ifc2objList[i][baseName][tempInner].get());
//        }
//    }
//    else
//    {
//        model->addChild(osgDB::readNodeFile(objFilePath.toStdString()));
//    }
//
//    if (!model)
//    {
//        qDebug() << "Model is  null";
//        return;
//    }
//
//    model->setName("model");
//    model->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
//
//    auto rotate = new osg::PositionAttitudeTransform;
//    rotate->addChild(model);
//
//    double rotationAngleX = osg::DegreesToRadians(rx);
//    double rotationAngleY = osg::DegreesToRadians(ry);
//    double rotationAngleZ = osg::DegreesToRadians(rz);
//    osg::Quat rotationQuat(rotationAngleX, osg::Vec3d(1.0, 0.0, 0.0),
//        rotationAngleY, osg::Vec3d(0.0, 1.0, 0.0),
//        rotationAngleZ, osg::Vec3d(0.0, 0.0, 1.0));
//    rotate->setAttitude(rotationQuat);
//
//    osg::ComputeBoundsVisitor computeBoundsVisitor;
//    rotate->accept(computeBoundsVisitor);
//
//    auto center = computeBoundsVisitor.getBoundingBox().center();
//
//    auto pan = new osg::PositionAttitudeTransform;
//    pan->addChild(rotate);
//    pan->setPosition(
//        osg::Vec3d(-center.x() * scale, -center.y() * scale, -center.z() * scale));
//    pan->setScale(
//        osg::Vec3d(scale, scale, scale));
//
//    auto radius = (computeBoundsVisitor.getBoundingBox().zMax() - computeBoundsVisitor.getBoundingBox().zMin()) / 2;
//
//    auto geoTransform = new osgEarth::GeoTransform();
//    geoTransform->setPosition(
//        osgEarth::GeoPoint(osgEarth::SpatialReference::get(epsg.toStdString()), longitude, latitude,
//            height + radius * scale,
//            osgEarth::AltitudeMode::ALTMODE_RELATIVE));
//
//    geoTransform->addChild(pan);
//
//    geoTransform->setName(name.toStdString());
//    geoTransform->setNodeMask(1);
//
//    try
//    {
//        MainWindowViewModel::getInstance()->modelWidget->root->addChild(geoTransform);
//    }
//    catch (_exception e)
//    {
//        QMessageBox::information(nullptr, "Warning", e.name);
//    }
//}
//
//void MainWindowViewModel_AddObjToOsgViewer(QList<QVariant>/*&*/* tmpVariantList)
//{
//    if (tmpVariantList != nullptr)
//    {
//        auto epsg = (*tmpVariantList)[0].toString();
//        double rx = (*tmpVariantList)[1].toDouble();
//        double ry = (*tmpVariantList)[2].toDouble();
//        double rz = (*tmpVariantList)[3].toDouble();
//        double tx = (*tmpVariantList)[4].toDouble();
//        double ty = (*tmpVariantList)[5].toDouble();
//        double tz = (*tmpVariantList)[6].toDouble();
//        double s = (*tmpVariantList)[7].toDouble();
//        QString objPath = (*tmpVariantList)[8].toString();
//        QString innerUrl = (*tmpVariantList)[9].toString();
//
//        QString name = "";
//        if (tmpVariantList->size() > 10)
//        {
//            /*QString*/ name = (*tmpVariantList)[10].toString();
//        }
//
//        MainWindowViewModel_AddObjToOsgViewer(rx, ry, rz, name, objPath, tx, ty, tz, s, epsg, innerUrl);
//    }
//}

static void GLDDirectory_GetAllSubDirs(const std::string& dir, std::vector<std::string>& subDirs)
{
    //
    subDirs.clear();

    for (auto& tmpDirectoryEntry : std::filesystem::directory_iterator(dir))
    {
        if (tmpDirectoryEntry.path().string() == "." || tmpDirectoryEntry.path().string() == "..")
        {
            continue;
        }

        if (tmpDirectoryEntry.is_directory())
        {
            subDirs.push_back(tmpDirectoryEntry.path().string());
        }
    }
}

static std::string GLDirectory_GetDirPathName(std::string filePath)
{
    size_t m = filePath.find_last_of('\\');
    if (m == std::string::npos)
    {
        return filePath;
    }

    size_t p = filePath.find_last_of('.');
    if (p != std::string::npos && p > m)
    {
        filePath.erase(p);
    }

    std::string dirPath = filePath;
    dirPath.erase(0, m + 1);

    return dirPath;
}

void createObliqueIndexes(std::string tmpOsgbFileDir)
{
    std::string dataDir = tmpOsgbFileDir + "/Data";

    osg::ref_ptr<osg::Group> group = new osg::Group();
    std::vector<std::string> subDirs;
    GLDDirectory_GetAllSubDirs(dataDir, subDirs);

    for (size_t i = 0; i < subDirs.size(); i++)
    {
        std::string name = GLDirectory_GetDirPathName(subDirs[i]);
        std::string path = subDirs[i] + "/" + name + ".osgb";

        if (!std::filesystem::exists(path))
        {
            continue;
        }

        osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(path);
        osg::ref_ptr<osg::PagedLOD> lod = new osg::PagedLOD();
        if (!node.valid())
        {
            continue;
        }

        //auto bs=node->computeBound();
        auto bs = node->getBound();
        auto c = bs.center();
        auto r = bs.radius();
        lod->setCenter(c);
        lod->setRadius(r);
        lod->setRangeMode(osg::LOD::RangeMode::PIXEL_SIZE_ON_SCREEN);
        osg::ref_ptr<osg::Geode> geode = new osg::Geode;
        geode->getOrCreateStateSet();
        lod->addChild(geode.get());

        std::string relativeFilePath = "./Data/" + name + "/" + name + ".osgb";

        lod->setFileName(0, "");
        lod->setFileName(1, relativeFilePath);

        lod->setRange(0, 0, 1.0);																							//第一层不可见
        lod->setRange(1, 1.0, FLT_MAX);

        lod->setDatabasePath("");

        group->addChild(lod);
    }

    std::string outputLodFile = tmpOsgbFileDir + "/Data.osgb";
    osgDB::writeNodeFile(*group, outputLodFile);
}

void MainWindowViewModel::addEntityToEarth()
{
    // 进度条
    QProgressBar progressBar;
    progressBar.setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint);
    progressBar.setRange(0, 100);
    progressBar.setValue(0);
    progressBar.show();

    int tmpCIMContentEntityNum = iCIMContent->getEntitiesCount();

    qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, tmpCIMContentEntityNum : " << tmpCIMContentEntityNum;

    for (int i = 0; i < iCIMContent->getEntitiesCount(); i++)
    {
        QThread::msleep(100);
        int progress = static_cast<int>(((double) (i) / (double) iCIMContent->getEntitiesCount()) * 100);
        progressBar.setValue(progress);
        QCoreApplication::processEvents();

        const auto entityCode = iCIMContent->getEntityCodeFromIndex(i);
        const auto entity = iCIMContent->getEntityFromCode(entityCode);

        Component::SpatialSystem *spatialSystem = NULL;
        Component::SpatialLocation *spatialLocation = NULL;
        Component::SpatialGeometry *spatialGeometry = NULL;

        auto count = 0;

        int tmpEntityComponentNum = entity->getComponentsCount();

        qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, tmpEntityComponentNum : " << tmpEntityComponentNum;

        for (int j = 0; j < entity->getComponentsCount(); j++)
        {
            const auto componentId = entity->getComponent(j);
            const auto component = iCIMContent->getComponentFromCode(componentId->getCode().c_str());

            qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, tmpComponentType : "
                                        << NNUOpenCIMComponent_ConvertComponentTypeToStr(component->getComponentType()).c_str();

            if (component->getComponentType() == NNU::OpenCIM::Component::SPATIALSYSTEM)
            {
                spatialSystem = (Component::SpatialSystem *) component;
                count++;
            }
            else if (component->getComponentType() == NNU::OpenCIM::Component::SPATIALLOCATION)
            {
                spatialLocation = (NNU::OpenCIM::Component::SpatialLocation *) component;
                count++;
            }
            else if (component->getComponentType() == NNU::OpenCIM::Component::SPATIALGEOMETRY)
            {
                spatialGeometry = (NNU::OpenCIM::Component::SpatialGeometry *) component;
                count++;
            }

            if (count == 3)
            {
                break;
            }
        }

        auto tmpVariantList = new QList<QVariant>;

        if (count != 3)
        {
            continue;
        }

        tmpVariantList->append(QString::fromStdString(spatialSystem->getEpsg()));
        tmpVariantList->append(spatialLocation->getRotateX());
        tmpVariantList->append(spatialLocation->getRotateY());
        tmpVariantList->append(spatialLocation->getRotateZ());
        tmpVariantList->append(spatialLocation->getPanX());
        tmpVariantList->append(spatialLocation->getPanY());
        tmpVariantList->append(spatialLocation->getPanZ());
        tmpVariantList->append(spatialLocation->getScale());

        if (spatialGeometry != NULL)
        {
            qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, tmpComponentType : "
                                                << NNUOpenCIMComponent_ConvertSpatialGeometryTypeToStr(spatialGeometry->getSpatialGeometryType()).c_str();
        }
        else if (spatialGeometry == NULL)
        {
            qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, spatialGeometry == NULL";
        }

        // 目前支持外部数据
        if (spatialGeometry->getSpatialGeometryType() == NNU::OpenCIM::Component::EXTERNALGEO)
        {
            const auto &externalDataId = spatialGeometry->getGeometry();
            auto externalData = MainWindowViewModel::getInstance()->iCIMContent->getExternalDataFromCode(externalDataId.c_str());

            qDebug() << "ACIMViewer_Lite, MainWindowViewModel::AddEntityToEarth, tmpExternalData::GetDataFormat() : "
                                                    << NNUOpenCIMCommon_ConvertDataFormatTypeToStr(externalData->getDataFormat()).c_str();

            NNU::OpenCIM::Common::DataFormat tmpExternalDataFormat = externalData->getDataFormat();

            // 目前支持OSGB、IFC
            if (externalData->getDataFormat() == NNU::OpenCIM::Common::OSGB
                || externalData->getDataFormat() == NNU::OpenCIM::Common::IFC
                || externalData->getDataFormat() == NNU::OpenCIM::Common::OBJ)
            {
                tmpVariantList->append(MainWindowViewModel::getInstance()->contentFileDir + "/" + QString::fromStdString(externalData->getOuterUrl()));
                tmpVariantList->append(QString::fromStdString(externalData->getInnerUrl()));
            }

            tmpVariantList->append(QString::fromStdString(entity->getId()->getComments()));
            tmpVariantList->append(QString::fromStdString(entity->getId()->getCode()));

            if (externalData->getDataFormat() == NNU::OpenCIM::Common::OSGB
                || tmpExternalDataFormat == NNU::OpenCIM::Common::IFC
                || tmpExternalDataFormat == NNU::OpenCIM::Common::DataFormat::OBJ)
            {
                osgbTool->run(tmpVariantList);
            }

            //if (externalData->getDataFormat() == NNU::OpenCIM::Common::OBJ)
            //{
            //    osgbTool->AddObjToViewer(in);
            //}
        }

        delete tmpVariantList;
    }
}
